package com.gitee.ixtf.cqrs.internal

import com.gitee.ixtf.cqrs.client.*
import com.gitee.ixtf.cqrs.kotlinx.ENTITY_ID_KEY
import com.gitee.ixtf.cqrs.verticle.EventSourcedEntity
import com.gitee.ixtf.guice.Jguice
import com.gitee.ixtf.guice.kotlinx.get
import com.github.benmanes.caffeine.cache.Caffeine
import com.sksamuel.aedile.core.asLoadingCache
import io.vertx.core.Future
import io.vertx.core.ThreadingModel.WORKER
import io.vertx.core.Vertx
import io.vertx.kotlin.core.deploymentOptionsOf
import io.vertx.kotlin.core.json.jsonObjectOf
import java.util.concurrent.TimeUnit
import kotlin.reflect.*
import kotlin.reflect.jvm.javaMethod

private val CACHE_ =
    Caffeine.newBuilder().asLoadingCache<KFunction<*>, EventSourcedEntity<*, *>> { methodRef ->
      val method = methodRef.javaMethod!!
      val clazz = method.declaringClass
      Jguice.getInstance(clazz) as EventSourcedEntity<*, *>
    }
private val CACHE =
    Caffeine.newBuilder()
        .maximumSize(100_000)
        .expireAfterAccess(1, TimeUnit.HOURS)
        .evictionListener<String, EventSourcedEntity<*, *>> { k, v, cause -> v?.invalidate() }
        .removalListener<String, EventSourcedEntity<*, *>> { k, v, cause -> v?.invalidate() }
        .build<String, EventSourcedEntity<*, *>>()

class EventSourcedEntityClientImpl(
    private val vertx: Vertx,
    private val entityId: String,
) : EventSourcedEntityClient {
  private fun createRef(methodRef: KFunction<*>): ComponentMethodRefImpl {
    val method = methodRef.javaMethod!!
    val clazz = method.declaringClass
    var deployFuture = Future.succeededFuture<Unit>()
    val instance =
        CACHE.get("${clazz.name}:$entityId") { key ->
          (Jguice.getInstance(clazz) as EventSourcedEntity<*, *>).also { verticle ->
            val config = jsonObjectOf(ENTITY_ID_KEY to entityId)
            val options = deploymentOptionsOf(config = config, threadingModel = WORKER)
            deployFuture =
                vertx
                    .deployVerticle(verticle, options)
                    .onFailure { CACHE.invalidate(key) }
                    .mapEmpty()
          }
        }
    val instanceFuture = deployFuture.map { instance }
    return ComponentMethodRefImpl(instanceFuture, methodRef)
  }

  override fun <T : EventSourcedEntity<*, *>, R> method(methodRef: KSuspendFunction1<T, R>) =
      createRef(methodRef) as ComponentMethodRef<R>

  override fun <T : EventSourcedEntity<*, *>, A1, R> method(
      methodRef: KSuspendFunction2<T, A1, R>
  ) = createRef(methodRef) as ComponentMethodRef1<A1, R>

  override fun <T : EventSourcedEntity<*, *>, A1, A2, R> method(
      methodRef: KSuspendFunction3<T, A1, A2, R>
  ) = createRef(methodRef) as ComponentMethodRef2<A1, A2, R>

  override fun <T : EventSourcedEntity<*, *>, A1, A2, A3, R> method(
      methodRef: KSuspendFunction4<T, A1, A2, A3, R>
  ) = createRef(methodRef) as ComponentMethodRef3<A1, A2, A3, R>

  override fun <T : EventSourcedEntity<*, *>, A1, A2, A3, A4, R> method(
      methodRef: KSuspendFunction5<T, A1, A2, A3, A4, R>
  ) = createRef(methodRef) as ComponentMethodRef4<A1, A2, A3, A4, R>

  override fun <T : EventSourcedEntity<*, *>, A1, A2, A3, A4, A5, R> method(
      methodRef: KSuspendFunction6<T, A1, A2, A3, A4, A5, R>
  ): ComponentMethodRef5<A1, A2, A3, A4, A5, R> =
      createRef(methodRef) as ComponentMethodRef5<A1, A2, A3, A4, A5, R>
}
